# gozerbot/users.py
#
#

""" bot users """

__copyright__ = 'this file is in the public domain'

## IMPORT SECTION

# gozerbot imports
from gozerbot.datadir import datadir
from gozerbot.utils.log import rlog
from gozerbot.utils.locking import lockdec
from gozerbot.utils.generic import stripident, stripidents
from gozerbot.utils.exception import handle_exception, exceptionmsg
from gozerbot.utils.generic import die, stripped
from gozerbot.utils.name import stripname
from gozerbot.persist.persist import Persist
from gozerbot.config import config
from gozerbot.jsonusers import JsonUsers

# basic imports
import re, types, os, time, thread, sqlalchemy

## END IMPORT

## LOCK SECTION

deletelock = thread.allocate_lock()
deletelocked = lockdec(deletelock)

## END LOCK

# see if we need to use the database or use the jsonusers

if True:
    if not config['nodb']:
        from gozerbot.database.alchemy import Session, UserHost, User, Perms, Statuses, query, getuser, byname, eagerload, dblocked

    else:
        def trans(func, *args, **kwargs):
            def transaction(*args, **kwargs):
                func(*args, **kwargs)
            return transaction


class DbUsers(object):

    """
         sqlalchemy backed users.

    """

    def __init__(self, ddir=None):
        self.datadir = ddir or datadir

    ### Misc. Functions
    def size(self):

        """
            return nr of users.

            .. literalinclude:: ../../gozerbot/users.py
                :pyobject: DbUsers.size
        """

        return int(query(User).count())
        
    def names(self):

        """
            get names of all users.

            :rtype: list .. list of usernames

            .. literalinclude:: ../../gozerbot/users.py
                :pyobject: DbUsers.names

        """
        a = query(User).all()
        return [n.name for n in a]

    @dblocked
    def merge(self, name, userhost):

        """
             add userhosts to user with name.

             :param session: the session to act upon
             :type session: sqllachemy.session.Session
             :param name: name of the user to merge with
             :type name: string
             :param userhost: userhosts to merge 
             :type userhost: list .. list of userhosts
             :rtype: boolean .. whether the merge succeeded

             .. literalinclude:: ../../gozerbot/users.py
                :pyobject: DbUsers.merge

        """

        user = byname(name)

        if user:

            if name not in user.userhosts:
                #Session.begin()
                user.userhosts.append(userhost)
                #Session.commit()
                Session.refresh(user)
                #Session.close()
                rlog(10, 'users', "%s merged with %s" % (userhost, name))
                return True

        return False

    def usersearch(self, userhost):

        """
            search for users with a userhost like the one specified.

            :param userhost: userhost to search for
            :type userhost: string
            :rtype: list .. list of (user.name, user.userhost) tuples

            .. literalinclude:: ../../gozerbot/users.py
                :pyobject: DbUsers.usersearch

        """

        n = query(UserHost).filter(UserHost.userhost.like('%%%s%%' % userhost)).all()
        return [(u.name, u.userhost) for u in n]

    ### Check functions
    def exist(self, name):

        """
            see if user with <name> exists.

            :param name: name of the user
            :rtype: boolean

            .. literalinclude:: ../../gozerbot/users.py
                :pyobject: DbUsers.exist

        """

        a = byname(name)

        if a:
            return True

        return False

    def allowed(self, userhost, perms, log=True):

        """
            check if user with userhosts is allowed to execute perm command.

            :param userhost: userhost to check
            :type userhost: string
            :param perms: the permissions to check            
            :type perms: list
            :param log: whether the check should be logged
            :type log: boolean
            :rtype: list .. list of matching permissions or None
 
            .. literalinclude:: ../../gozerbot/users.py
                :pyobject: DbUsers.allowed

        """

        if not type(perms) == types.ListType:
            perms = [perms, ]

        if 'ANY' in perms:
            return ['ANY', ]

        res = None
        user = getuser(userhost)

        if not user:

            if log:
                rlog(10, 'users', '%s userhost denied' % userhost)
            return res

        else:
            uperms = set(user.perms)
            sperms = set(perms)
            intersection = sperms.intersection(uperms)
            res = list(intersection) or None

        if not res and log:
            rlog(10, 'users', "%s perm %s denied" % (userhost, str(perms)))

        if res and log:
            rlog(10, 'users', 'allowed %s %s perm' % (userhost, str(perms)))

        return res

    def permitted(self, userhost, who, what):

        """
            check if (who,what) is in users permit list.

            :param userhost: userhost to check permits for
            :type userhost: string
            :param who: who to permit
            :type who: string
            :param what: what to allow
            :type what: string

            .. literalinclude:: ../../gozerbot/users.py
                :pyobject: DbUsers.permitted
        """

        user = getuser(userhost)
        res = False

        if user:
            if '%s %s' % (who, what) in user.permits:
                res = True

        return res

    def status(self, userhost, status):

        """
            check if user with <userhost> has <status> set.

            .. literalinclude:: ../../gozerbot/users.py
                :pyobject: DbUsers.status

        """

        user = getuser(userhost)
        res = False

        if user:
            if status.upper() in user.statuses:
                res = True

        return res

    def gotuserhost(self, name, userhost):

        """
            check if user has userhost.

            .. literalinclude:: ../../gozerbot/users.py
                :pyobject: DbUsers.gotuserhost

        """

        user = byname(name)

        if user:
            return userhost in user.userhosts

        return False

    def gotperm(self, name, perm):

        """
            check if user has permission.


            .. literalinclude:: ../../gozerbot/users.py
                :pyobject: DbUsers.gotperm
        """

        user = byname(name)

        if user:
            return perm.upper() in user.perms

        return False

    def gotpermit(self, name, permit):

        """
            check if user permits something.  permit is a [who, what] list. 


            .. literalinclude:: ../../gozerbot/users.py
                :pyobject: DbUsers.gotpermit

        """

        user = byname(name)

        if user:
            return '%s %s' % permit in user.permits

        return ""

    def gotstatus(self, name, status):

        """
            check if user has status.

            .. literalinclude:: ../../gozerbot/users.py
                :pyobject: DbUsers.gotstatus

        """

        user = byname(name)
        if user:
            return status.upper() in user.statuses

        return False

    ### Get Functions
    def getname(self, userhost):

        """
            get name of user belonging to <userhost>.

            .. literalinclude:: ../../gozerbot/users.py
                :pyobject: DbUsers.getname

        """

        user = getuser(userhost)

        if user:
            return str(user.name)

        return ""

    def gethosts(self, userhost):

        """ 
            return the userhosts of the user associated with the 
            specified userhost.

            .. literalinclude:: ../../gozerbot/users.py
                :pyobject: DbUsers.gethosts
        """

        user = getuser(userhost)

        if user:
            return list(user.userhosts)

        return []
    
    def getemail(self, userhost):

        """
            return the email of the specified userhost.

            .. literalinclude:: ../../gozerbot/users.py
                :pyobject: DbUsers.getemail
        """

        user = getuser(userhost)

        if user:
            if user.email:
                return str(user.email[0])

        return ""

    def getperms(self, userhost):

        """
            return permissions of user.

            .. literalinclude:: ../../gozerbot/users.py
                :pyobject: DbUsers.getperms

        """

        user = getuser(userhost)

        if user:
            return list(user.perms)

        return []

    def getpermits(self, userhost):

        """
            return permits of the specified userhost.

            .. literalinclude:: ../../gozerbot/users.py
                :pyobject: DbUsers.getpermits

        """

        user = getuser(userhost)

        if user:
            return list(user.permits)

        return []

    def getstatuses(self, userhost):

        """
            return the list of statuses for the specified userhost.

            .. literalinclude:: ../../gozerbot/users.py
                :pyobject: DbUsers.getstatuses

        """

        user = getuser(userhost)

        if user:
            return list(user.statuses)

    def getuserhosts(self, name):

        """
            return the userhosts associated with the specified user.

            .. literalinclude:: ../../gozerbot/users.py
                :pyobject: DbUsers.getuserhosts
        """

        user = byname(name)

        if user:
            return list(user.userhosts)

    def getuseremail(self, name):

        """
            get email of user.

            .. literalinclude:: ../../gozerbot/users.py
                :pyobject: DbUsers.getuseremail

        """

        user = byname(name)

        if user:
            if user.email:
                return str(user.email[0])

    def getuserperms(self, name):

        """
            return permission of user.

            .. literalinclude:: ../../gozerbot/users.py
                :pyobject: DbUsers.getuserperms
        """

        user = byname(name)

        if user:
            return list(user.perms)

    def getuserpermits(self, name):
 
        """
            return permits of user.

            .. literalinclude:: ../../gozerbot/users.py
                :pyobject: DbUsers.getuserpermits
        """

        user = byname(name)

        if user:
            return list(user.permits)

    def getuserstatuses(self, name):

        """
            return the list of statuses for the specified user.

            .. literalinclude:: ../../gozerbot/users.py
                :pyobject: DbUsers.getuserstatuses
        """

        user = byname(name)

        if user:
            return list(user.statuses)

    def getpermusers(self, perm):

        """
            return all users that have the specified perm.

            .. literalinclude:: ../../gozerbot/users.py
                :pyobject: DbUsers.getpermusers

        """
        n = query(Perms).filter(Perms.perm==perm.upper()).all()
        return [user.name for user in n]

    def getstatususers(self, status):

        """
            return all users that have the specified status.

            .. literalinclude:: ../../gozerbot/users.py
                :pyobject: DbUsers.getstatususers

        """

        n = query(Statuses).filter(Statuses.status==status.upper()).all()
        return [user.name for user in n]

    ### Set Functions

    @dblocked
    def setemail(self, name, email):

        """
            set email of user.

            .. literalinclude:: ../../gozerbot/users.py
                :pyobject: DbUsers.setemail
        """

        user = byname(name)

        if user:
            #Session.begin()
            try:
                user.email.remove(email)
            except:
                pass
            user.email.insert(0, email)
            Session.refresh(user)
            #Session.commit()
            #Session.close()
            return True

        return False

    ### Add functions

    @dblocked
    def add(self, name, userhosts, perms):

        """
            add an user. session argument is provided by @trans

            :param name: name of the user
            :type name: string
            :param userhosts: userhosts to add
            :type userhosts: list .. list of userhosts
            :param perms: the permissions to add
            :type perms: list .. list of permissions

            .. literalinclude:: ../../gozerbot/users.py
                :pyobject: DbUsers.add

        """

        if type(userhosts) != types.ListType:
            rlog(10, 'users', 'i need a list of userhosts')
            return False

        name = stripname(name.lower())

        if not os.path.isdir(self.datadir + os.sep + 'users'):
            os.mkdir(self.datadir + os.sep + 'users')

        if not os.path.isdir(self.datadir + os.sep + 'users' +  os.sep + name):
            os.mkdir(self.datadir + os.sep + 'users' +  os.sep + name)

        user = byname(name)

        if not user:

            try:
                #Session.begin()
                newuser = User(name=name)
                newuser.userhosts.extend(userhosts)
                newuser.perms.extend(perms)
                Session.add(newuser)
                #Session.commit()
                #Session.close()
            except sqlalchemy.exc.IntegrityError, ex:
                if 'not unique' in str(ex):
                    rlog(10, 'users', '%s %s already exists' % (name, userhosts))
                    Session.close()
                    return True
                else:
                    raise
            except Exception, ex:
                raise

            rlog(10, 'users', '%s %s %s added to user database' % (name, userhosts, perms))

        return True

    @dblocked
    def addemail(self, userhost, email):

        """
            add an email address to the userhost.

            .. literalinclude:: ../../gozerbot/users.py
                :pyobject: DbUsers.addemail

        """

        user = getuser(userhost)

        if user:
            #Session.begin()
            user.email.append(email)
            Session.refresh(user)
            #Session.commit()
            #Session.close()
            rlog(10, 'users', '%s (%s) added to email' % (email, userhost))
            return True

        return False

    @dblocked
    def addperm(self, userhost, perm):

        """
            add the specified perm to the userhost.

            .. literalinclude:: ../../gozerbot/users.py
                :pyobject: DbUsers.addperm

        """

        user = getuser(userhost)

        if user:
            #Session.begin()
            user.perms.append(perm.upper())
            Session.refresh(user)
            #Session.commit()
            #Session.close()
            rlog(10, 'users', '%s perm %s added' % (userhost, perm))
            return True

        return False

    @dblocked
    def addpermit(self, userhost, permit):

        """
            add the given [who, what] permit to the given userhost.

            .. literalinclude:: ../../gozerbot/users.py
                :pyobject: DbUsers.addpermit

        """

        user = getuser(userhost)

        if user:
            p = '%s %s' % permit
            #Session.begin()
            user.permits.append(p)
            Session.refesh(user)
            #Session.commit()
            #Session.close()
            rlog(10, 'users', '%s permit %s added' % (userhost, p))
            return True

        return False

    @dblocked
    def addstatus(self, userhost, status):

        """
            add status to given userhost.

            .. literalinclude:: ../../gozerbot/users.py
                :pyobject: DbUsers.addstatus

        """

        user = getuser(userhost)

        if user:
            #Session.begin()
            user.statuses.append(status.upper())
            #Session.commit()
            Session.refresh(user)
            #Session.close()
            rlog(10, 'users', '%s status %s added' % (name, status))
            return True

        return False

    @dblocked
    def adduserhost(self, name, userhost):

        """
            add userhost.

            .. literalinclude:: ../../gozerbot/users.py
                :pyobject: DbUsers.adduserhost

        """

        user = byname(name)
        #Session.begin()
        if not user:
            user = User(name=name)
            Session.add(user)
       
        user.userhosts.append(userhost)
        #Session.commit()
        Session.refresh(user)
        #Session.close()
        rlog(10, 'users', '%s (%s) added to userhosts' % (name, userhost))
        return True

    @dblocked
    def adduseremail(self, name, email):

        """
            add email to specified user.

            .. literalinclude:: ../../gozerbot/users.py
                :pyobject: DbUsers.adduseremail

        """

        user = byname(name)

        if user:
            #Session.begin()
            user.email.append(email)
            #Session.commit()
            Session.refresh(user)
            #Session.close()
            rlog(10, 'users', '%s email %s added' % (name, email))
            return True

    @dblocked
    def adduserperm(self, name, perm):

        """
            add permission.

            .. literalinclude:: ../../gozerbot/users.py
                :pyobject: DbUsers.adduserperm

        """

        user = byname(name)

        if user:
            #Session.begin()
            perm = perm.upper()
            user.perms.append(perm)
            #Session.commit()
            Session.refresh(user)
            #Session.close()
            rlog(10, 'users', '%s perm %s added' % (name, perm))
            return True

        return False

    @dblocked
    def adduserpermit(self, name, who, permit):

        """ 
            add (who, what) permit tuple to specified user.

            .. literalinclude:: ../../gozerbot/users.py
                :pyobject: DbUsers.adduserpermit

        """

        user = byname(name)

        if user:
            p = '%s %s' % (who, permit)
            #Session.begin()
            user.permits.append(p)
            #Session.commit()
            Session.refresh(user)
            #Session.close()
            rlog(10, 'users', '%s permit %s added' % (name, p))
            return True

        return False

    @dblocked
    def adduserstatus(self, name, status):

        """
            add status to given user.

            .. literalinclude:: ../../gozerbot/users.py
                :pyobject: DbUsers.adduserstatus

        """

        user = byname(name)

        if user:
            #Session.begin()
            user.statuses.append(status.upper())
            #Session.commit()
            Session.refresh(user)
            #Session.close()
            rlog(10, 'users', '%s status %s added' % (name, status))
            return True

        return False

    @dblocked
    def addpermall(self, perm): 

        """
            add permission to all users.

            .. literalinclude:: ../../gozerbot/users.py
                :pyobject: DbUsers.addpermall
        """

        users = query(User).all()

        if users:
            #Session.begin()
            for user in users:
                user.perms.append(perm.upper())
                Session.refresh(user)
            #Session.commit()
            #Session.close()

    ### Delete functions

    @dblocked
    def delemail(self, userhost, email):

        """
            delete email from userhost.

            .. literalinclude:: ../../gozerbot/users.py
                :pyobject: DbUsers.delemail

        """

        user = getuser(userhost)

        if user:
            if email in user.emails:
                #Session.begin()
                user.emails.remove(email)
                #Session.commit()
                Session.refresh(user)
                #Session.close()
                return True

        return False

    @dblocked
    def delperm(self, userhost, perm):

        """
            delete perm from userhost.

            .. literalinclude:: ../../gozerbot/users.py
                :pyobject: DbUsers.delperm

        """

        user = getuser(userhost)

        if user:
            p = perm.upper()

            if p in user.perms:
                #Session.begin()
                user.perms.remove(p)
                #Session.commit()
                Session.refresh(user)
                #Session.close()
                return True

        return False
            
    @dblocked
    def delpermit(self, userhost, permit):

        """
            delete permit from userhost.

            .. literalinclude:: ../../gozerbot/users.py
                :pyobject: DbUsers.delpermit
        """

        user = getuser(userhost)

        if user:
            p = '%s %s' % permit

            if p in user.permits:
                #Session.begin()
                user.permits.remove(p)
                #Session.commit()
                Session.refresh(user)
                #Session.close()
                return True

        return False

    @dblocked
    def delstatus(self, userhost, status):

        """
            delete status from userhost.

            .. literalinclude:: ../../gozerbot/users.py
                :pyobject: DbUsers.delstatus

        """

        user = getuser(userhost)

        if user:
            st = status.upper()

            if st in user.statuses:
                #Session.begin()
                user.statuses.remove(st)
                #Session.commit()
                Session.refresh(user)
                #Session.close()
                return True

        return False

    @dblocked
    def delete(self, name):

        """ 
            delete user with name. 

            .. literalinclude:: ../../gozerbot/users.py
                :pyobject: DbUsers.delete

        """
        name = stripname(name)
        user = byname(name)

        if user:
            rlog(10, 'users', "deleting %s" % name)
            #Session.begin()
            Session.delete(user)
            #Session.commit()
            #Session.close()
            return True

        return False

    @dblocked
    def deluserhost(self, name, userhost):

        """
            delete the userhost entry.


            .. literalinclude:: ../../gozerbot/users.py
                :pyobject: DbUsers.deluserhost
        """

        user = byname(name)

        if user:
            if userhost in user.userhosts:
                #Session.begin()
                user.userhosts.remove(userhost)
                Session.refresh(user)
                #Session.commit()
                #Session.close()
                rlog(10, 'users', '%s userhost %s deleted' % (name, userhost))
                return True

        return False

    @dblocked
    def deluseremail(self, name, email):

        """
            delete email.

            .. literalinclude:: ../../gozerbot/users.py
                :pyobject: DbUsers.deluseremail

        """

        user = byname(name)

        if user:
            if email in user.email:
                #Session.begin()
                user.email.remove(email)
                Session.refresh(user)
                #Session.commit()
                #Session.close()
                rlog(10, 'users', '%s email %s deleted' % (name, email))
                return True

        return False

    @dblocked
    def deluserperm(self, name, perm):

        """
             delete permission.

            .. literalinclude:: ../../gozerbot/users.py
                :pyobject: DbUsers.deluserperm

        """

        user = byname(name)

        if user:
            p = perm.upper()

            if p in user.perms:
                #Session.begin()
                user.perms.remove(p)
                Session.refresh(user)
                #Session.commit()
                #Session.close()
                rlog(10, 'users', '%s perm %s deleted' % (name, p))
                return True

        return False

    @dblocked
    def deluserpermit(self, name, permit):

        """
            delete permit.

            .. literalinclude:: ../../gozerbot/users.py
                :pyobject: DbUsers.deluserpermit

        """

        user = byname(name)

        if user:
            p = '%s %s' % permit

            if p in user.permits:
                #Session.begin()
                user.permits.remove(p)
                Session.refresh(user)
                #Session.commit()
                #Session.close()
                rlog(10, 'users', '%s permit %s deleted' % (name, p))
                return True

        return False

    @dblocked
    def deluserstatus(self, name, status):

        """
            delete the status from the given user.

            .. literalinclude:: ../../gozerbot/users.py
                :pyobject: DbUsers.deluserstatus

        """

        user = byname(name)

        if user:
            st = status.upper()

            if st in user.statuses:
                #Session.begin()
                user.statuses.remove(status)
                Session.refresh(user)
                #Session.commit()
                #Session.close()
                rlog(10, 'users', '%s status %s deleted' % (name, st))
                return True

        return False

    @dblocked
    def delallemail(self, name):

        """
            Delete all emails of the specified user.


            .. literalinclude:: ../../gozerbot/users.py
                :pyobject: DbUsers.delallemail

        """

        user = byname(name)

        if user:
            #Session.begin()
            user.email = []
            Session.refresh(user)
            #Session.commit()
            #Session.close()
            rlog(10, 'users', '%s emails deleted' % (name, ))
            return True

        return False

    @dblocked
    def delpermall(self, perm):

        """
            delete permission from all users.

            .. literalinclude:: ../../gozerbot/users.py
                :pyobject: DbUsers.delpermall

        """

        users = query(User).options(eagerload('perms')).all()

        for user in users:

            if user.name != 'owner':

                try:
                    #Session.begin()
                    user.perms.remove(perm)
                    Session.refresh(user)
                    #Session.commit()
                    #Session.close()
                except ValueError:
                    pass

        return True

    def make_owner(self, userhosts):

        """
            see if owner already has a user account if not merge otherwise 
            add.

            :param userhosts: userhosts to inititalize owner with
            :type userhosts: list or string

            .. literalinclude:: ../../gozerbot/users.py
                :pyobject: DbUsers.make_owner

        """

        owner = []

        if type(userhosts) != types.ListType:
            owner.append(userhosts)
        else:
            owner = userhosts

        for userhost in owner:
            username = self.getname(unicode(userhost))

            if not username:
                if not self.merge('owner', unicode(userhost)):
                    self.add('owner', [unicode(userhost), ], ['USER', 'OPER'])
            elif username != 'owner':
                self.delete(username)
                if not self.merge('owner', unicode(userhost)):
                    self.add('owner', [unicode(userhost), ], ['USER', 'OPER'])
               
## INIT SECTION

# default to database

if not config['nodb']:
    users = DbUsers()
else:
    # otheriwse use json users
    users = JsonUsers(datadir + os.sep + (config['jsonuser'] or 'users.json'))

## END INIT
